package demo.game.chess.common;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.allen_sauer.gwt.log.client.Log;

import demo.game.chess.common.BoardChange.EnumChangeType;

public class ChessBoard {

	private final List<MoveHistory> historyList = new ArrayList<MoveHistory>();

	/**
	 * Pieces in the board,
	 * organized according the the white user.
	 */
	private final AbstractChessPiece[][] table = new AbstractChessPiece[8][8];

	private ChessMove currentAttack = null;

	private ChessMove possibleDefensiveMove = null;

	private EnumChessColor currentTurn = EnumChessColor.white;

	public ChessBoard() {

	}

	public ChessBoard(final ChessBoard original, final ChessMove move) {
		copyFrom(original);
		applyMove(move);
	}

	private List<BoardChange> applyMove(final ChessMove move) {
		//AbstractChessPiece movingPiece = getPieceAt(move.getOriginCell());

		List<BoardChange> changes = getBoardChanges(move);
		for (BoardChange c : changes) {
			if (c.getChangeType() == EnumChangeType.add) {
				//				Log.debug("applyMove add " + c.getPiece() + " at "
				//						+ c.getCell());
				this.table[c.getCell().getRow()][c.getCell().getColumn()] = c
						.getPiece();
			} else if (c.getChangeType() == EnumChangeType.remove) {
				//				Log.debug("applyMove remove "
				//						+ this.table[c.getCell().getRow()][c.getCell()
				//								.getColumn()] + " at " + c.getCell());
				this.table[c.getCell().getRow()][c.getCell().getColumn()] = null;

			}
		}

		this.historyList.add(new MoveHistory(move, changes));

		this.currentTurn = this.currentTurn.getOpposite();

		return changes;
	}

	public List<BoardChange> confirmMove(final ChessMove move) {
		AbstractChessPiece movingPiece = getPieceAt(move.getOriginCell());

		List<BoardChange> changes = applyMove(move);

		updateAttackState(movingPiece.getColor().getOpposite());

		return changes;
	}

	public void copyFrom(final ChessBoard original) {
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < 8; j++) {
				this.table[i][j] = original.getPieceAt(i, j);
			}
		}
		this.currentTurn = original.currentTurn;
	}

	/**
	 * 
	 * @param i cell (black on top, white on bottom).
	 * @param j
	 * @return
	 */
	protected AbstractChessPiece createPiece(final int i, final int j) {

		for (EnumChessPiece p : EnumChessPiece.values()) {
			if (p.hasInitialPositionAt(new ChessCell(i, j))) {
				AbstractChessPiece instance = p.createInstance(new ChessCell(i,
						j));
				return instance;
			}
		}
		return null;
	}

	/**
	 * Checking if the move is valid and the king is not under attack.
	 * @param chessCell TODO
	 * @param target
	 * @return
	 */
	public ChessMove createValidMove(final ChessCell chessCell,
			final ChessCell target) throws MoveException {
		AbstractChessPiece movingPiece = getPieceAt(chessCell);

		if (null == movingPiece) {
			Log.warn(getClass() + " createValidMove no piece at origin cell "
					+ chessCell);
			return null;
		} else {
			ChessMove move = movingPiece.createValidMove(this, chessCell,
					target);
			if (null == move) {
				return null;
			}
			ChessBoard predictBoard = new ChessBoard(this, move);

			//Log.debug("finding king attack");
			ChessMove attack = predictBoard.findKingAttack(movingPiece
					.getColor());

			if (attack != null) {
				Log.debug(getClass() + " King is attacked by " + attack);
				throw new KingAttackException(move, predictBoard, attack);
			} else {
				Log.debug(getClass() + " returning " + move);
				return move;
			}
		}
	}

	private ChessMove findDefensiveMove(final EnumChessColor kingSide) {
		// complexity (max 16 pieces)* (max 7+7+7 movable target cells(case of queen)) * (1 : AbstractChessPiece.createValidMove) 
		for (int i = 0; i < this.table.length; i++) {
			for (int j = 0; j < this.table[i].length; j++) {
				if (this.table[i][j] != null
						&& this.table[i][j].getColor() == kingSide) {

					for (int ii = 0; ii < this.table.length; ii++) {
						for (int jj = 0; jj < this.table[ii].length; jj++) {
							ChessMove m;
							try {
								m = createValidMove(new ChessCell(i, j),
										new ChessCell(ii, jj));
								if (null != m) {
									Log.debug("found a defensive move by "
											+ this.table[i][j] + " " + m);
									return m;
								}
							} catch (MoveException e) {
								Log.debug("finding defensive move failed: "
										+ e.getMessage());
							}

						}
					}
				}
			}
		}
		return null;
	}

	/**
	 * Return the move that can attack the king.
	 * 
	 * @param predictBoard
	 * @param kingColor
	 * @return
	 */
	ChessMove findKingAttack(final EnumChessColor kingColor) {
		List<ChessCell> kingPosition = locate(EnumChessPiece.king, kingColor);
		assert kingPosition.size() == 1;
		return findKingAttackInSquare(kingColor, kingPosition.get(0));
	}

	/**
	 * Used for validating the castling move.
	 * @param kingColor
	 * @param kingSquare
	 * @return
	 */
	public ChessMove findKingAttackInSquare(final EnumChessColor kingColor,
			final ChessCell kingSquare) {
		for (int i = 0; i < this.table.length; i++) {
			for (int j = 0; j < this.table[i].length; j++) {
				if (null != this.table[i][j]
						&& this.table[i][j].getColor() != kingColor) {
					// Log.debug("checking attack by " + this.table[i][j]);
					ChessMove m;
					try {
						m = this.table[i][j].createValidMove(this,
								new ChessCell(i, j), kingSquare);
						if (null != m) {
							return m;
						}
					} catch (MoveException e) {
						// no attack to the cell.
					}

				}
			}
		}
		return null;
	}

	private List<BoardChange> getBoardChanges(final ChessMove chessMove) {
		List<BoardChange> result = new ArrayList<BoardChange>();

		AbstractChessPiece currentPiece = getPieceAt(chessMove.getOriginCell());

		AbstractChessPiece capturedPiece = getPieceAt(chessMove.getTargetCell());

		if (null != capturedPiece) {
			result.add(new BoardChange(EnumChangeType.remove, chessMove
					.getTargetCell(), capturedPiece));
		}
		result.add(new BoardChange(EnumChangeType.remove, chessMove
				.getOriginCell(), currentPiece));

		if (null != chessMove.getPawnPromotion()) {
			result.add(new BoardChange(EnumChangeType.add, chessMove
					.getTargetCell(), chessMove.getPawnPromotion()
					.createInstance(currentPiece.getInitialPosition())));

		} else {
			result.add(new BoardChange(EnumChangeType.add, chessMove
					.getTargetCell(), currentPiece));

		}

		if (null != chessMove.getCastling()) {
			AbstractChessPiece rook = getPieceAt(chessMove.getCastling()
					.getOriginRookCell());

			result.add(new BoardChange(EnumChangeType.remove, chessMove
					.getCastling().getOriginRookCell(), rook));
			result.add(new BoardChange(EnumChangeType.add, chessMove
					.getCastling().getTargetRookCell(), rook));

		}

		return result;
	}

	public ChessMove getCurrentAttack() {
		return this.currentAttack;
	}

	public EnumChessColor getCurrentTurn() {
		return this.currentTurn;
	}

	public MoveHistory getLastMove() {
		if (this.historyList.isEmpty()) {
			return null;
		} else {
			return this.historyList.get(this.historyList.size() - 1);
		}
	}

	public AbstractChessPiece getPieceAt(final ChessCell cell) {
		return this.table[cell.getRow()][cell.getColumn()];
	}

	public AbstractChessPiece getPieceAt(final int i, final int j) {
		return this.table[i][j];
	}

	public ChessMove getPossibleDefensiveMove() {
		return this.possibleDefensiveMove;
	}

	public boolean hasPieceMoved(final ChessCell originalPosition) {
		for (MoveHistory h : this.historyList) {
			if (h.getMove().getOriginCell().equals(originalPosition)) {
				return true;
			}
		}
		return false;
	}

	public void init() {
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < 8; j++) {
				this.table[i][j] = createPiece(i, j);
			}
		}
	}

	public boolean isCheck() {
		return this.currentAttack != null;
	}

	public boolean isCheckMate() {
		return isCheck() && this.possibleDefensiveMove == null;
	}

	public ChessCell locate(final AbstractChessPiece piece) {
		for (int i = 0; i < this.table.length; i++) {
			for (int j = 0; j < this.table[i].length; j++) {
				if (piece.equals(this.table[i][j])) {
					return new ChessCell(i, j);
				}
			}
		}
		return null;
	}

	public List<ChessCell> locate(final EnumChessPiece pieceType,
			final EnumChessColor color) {
		List<ChessCell> result = new ArrayList<ChessCell>();
		// Log.debug("Locating " + pieceType + " " + color);
		for (int i = 0; i < this.table.length; i++) {
			for (int j = 0; j < this.table[i].length; j++) {
				//				Log.debug("comparing " + i + " " + j + " " + this.table[i][j]);
				if (null != this.table[i][j]
						&& this.table[i][j].getType() == pieceType
						&& this.table[i][j].getColor() == color) {
					result.add(new ChessCell(i, j));
				}
			}
		}
		// Log.debug("Locating " + pieceType + " " + color + " return " + result);
		return result;
	}

	public MoveHistory undoLastMove() {
		if (this.historyList.isEmpty()) {
			return null;
		}
		MoveHistory h = this.historyList.remove(this.historyList.size() - 1);
		Collections.reverse(h.getChanges());
		for (BoardChange c : h.getChanges()) {
			//			Log.debug(getClass() + " undo : " + c);
			if (c.getChangeType() == EnumChangeType.remove) {
				this.table[c.getCell().getRow()][c.getCell().getColumn()] = c
						.getPiece();
			} else if (c.getChangeType() == EnumChangeType.add) {
				this.table[c.getCell().getRow()][c.getCell().getColumn()] = null;
			}
		}

		this.currentTurn = this.currentTurn.getOpposite();

		return h;
	}

	private void updateAttackState(final EnumChessColor kingSide) {
		this.currentAttack = findKingAttack(kingSide);

		if (null != this.currentAttack) {
			this.possibleDefensiveMove = findDefensiveMove(kingSide);
		} else {
			this.possibleDefensiveMove = null;
		}
	}

}
